// Copyright (C) 2022 即时通讯网(52im.net) & Jack Jiang.
// The RainbowChat Project. All rights reserved.
//
// 【本产品为著作权产品，合法授权后请放心使用，禁止外传！】
// 【本次授权给：<广西木子科技有限公司>，授权编号：<NT220402151538>，代码指纹：<A.648883738.885>，技术对接人微信：<ID: Lingmuziyi>】
// 【授权寄送：<收件：李先生、地址：南宁市科园西十路11号国电智能大厦1101F、电话：17736659550、邮箱：yingshashou@vip.qq.com>】
//
// 【本系列产品在国家版权局的著作权登记信息如下】：
// 1）国家版权局登记名(简称)和权证号：RainbowChat    （证书号：软著登字第1220494号、登记号：2016SR041877）
// 2）国家版权局登记名(简称)和权证号：RainbowChat-Web（证书号：软著登字第3743440号、登记号：2019SR0322683）
// 3）国家版权局登记名(简称)和权证号：RainbowAV      （证书号：软著登字第2262004号、登记号：2017SR676720）
// 4）国家版权局登记名(简称)和权证号：MobileIMSDK-Web（证书号：软著登字第2262073号、登记号：2017SR676789）
// 5）国家版权局登记名(简称)和权证号：MobileIMSDK    （证书号：软著登字第1220581号、登记号：2016SR041964）
// * 著作权所有人：江顺/苏州网际时代信息科技有限公司
//
// 【违法或违规使用投诉和举报方式】：
// 联系邮件：jack.jiang@52im.net
// 联系微信：hellojackjiang
// 联系QQ号：413980957
// 授权说明：http://www.52im.net/thread-1115-1-1.html
// 官方社区：http://www.52im.net
package com.eva.framework.httpfile;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;

import javax.servlet.http.HttpServletResponse;

import com.eva.epc.common.util.CommonUtils;

/**
 * 实现客户端的Http文件下载的实用类。
 * <p>
 * <b>补充：</b>客户端上传文件时，Android、Web其实是可以通过在上传URL中将参数带过来，但对于iOS
 * 端的 AFNetworking 3.0库来说这样做会直接导致客户端卡死（可能是AFN的bug，也可能是ios
 * 的bug）  ，所以目前RainbowChat服务端的文件上传处理类中，是从Multipart Content中读
 * 取参数（而不是url的参数），客户端也应该把参数放到Multipart Content中传过来，而不是url中。
 *
 * @author Jack Jiang(http://www.52im.net/space-uid-1.html)
 */
public class FileDownload {
    public static final String CONTENT_TYPE_DEFAULT = "application/octet-stream;charset=UTF-8";
    public static final String CONTENT_TYPE_JPEG = "image/jpeg";
    public static final String CONTENT_TYPE_MP4 = "video/mp4";
    public static final String CONTENT_TYPE_MP3 = "audio/mpeg";

    /**
     * 以在服务端的文件名方式直接下载给客户端.
     *
     * @param res
     * @param fileRealPath
     * @throws Exception
     */
    public static void download(HttpServletResponse res, String fileRealPath)
            throws Exception {
        download(res, fileRealPath, null);
    }

    /**
     * 以在服务端的文件名方式直接下载给客户端.
     *
     * @param res
     * @param file 要下载的文件对象
     * @throws Exception
     */
    public static void download(HttpServletResponse res, File file) throws Exception {
        download(res, file, null);
    }

    /**
     * 从服务端下载文件(给HTTP客户端).
     * <br>
     * Content-Type is default:{@link CONTENT_TYPE_DEFAULT}.
     *
     * @param res          响应实例
     * @param fileRealPath 服务端的文件绝对路径
     * @param displayName  客户端收到文件接收请求时将看到的文件显示名
     * @throws Exception
     */
    public static void download(HttpServletResponse res, String fileRealPath
            , String displayName) throws Exception {
        download(res, fileRealPath, displayName, CONTENT_TYPE_DEFAULT, true);
    }

    /**
     * 从服务端下载文件(给HTTP客户端).
     * <br>
     * Content-Type is default:{@link CONTENT_TYPE_DEFAULT}.
     *
     * @param res         响应实例
     * @param file        要下载的文件对象
     * @param displayName 客户端收到文件接收请求时将看到的文件显示名
     * @throws Exception
     */
    public static void download(HttpServletResponse res, File file
            , String displayName) throws Exception {
        download(res, file, displayName, CONTENT_TYPE_DEFAULT, true);
    }

    /**
     * 从服务端下载文件(给HTTP客户端).
     *
     * @param res               响应实例
     * @param fileRealPath      服务端的文件绝对路径
     * @param displayName       客户端收到文件接收请求时将看到的文件显示名
     * @param contentType       为null则表示返回结果的header中不加contet-type属性
     * @param usedForAttachment 此参数只对浏览器端调用时起效：true表示在http头是标记作为附件，否则将直接在浏览器里被打开，
     *                          有关Content-Disposition 属性的说明详见RFC1806：http://www.rfc-editor.org/rfc/rfc1806.txt
     * @throws Exception
     */
    public static void download(HttpServletResponse res, String fileRealPath
            , String displayName, String contentType, boolean usedForAttachment)
            throws Exception {
        download(res, new File(fileRealPath), displayName, contentType, usedForAttachment);
    }

    /**
     * 从服务端下载文件(给HTTP客户端).
     *
     * @param res               响应实例
     * @param file              要下载的文件对象
     * @param displayName       客户端收到文件接收请求时将看到的文件显示名
     * @param contentType       为null则表示返回结果的header中不加contet-type属性
     * @param usedForAttachment 此参数只对浏览器端调用时起效：true表示在http头是标记作为附件，否则将直接在浏览器里被打开，
     *                          有关Content-Disposition 属性的说明详见RFC1806：http://www.rfc-editor.org/rfc/rfc1806.txt
     * @throws Exception
     */
    public static void download(HttpServletResponse res, File file
            , String displayName, String contentType, boolean usedForAttachment) throws Exception {
        Exception ex = null;
//		File file = new File(fileRealPath);
        if (!file.exists()) {
            new RuntimeException("对不起,没有找到你要的文件!");
        }

        //返回给客户看到的文件名
        String dispName;
        if (CommonUtils.isStringEmpty(displayName))
            dispName = file.getName();
        else
            dispName = displayName;

        // http头中放入文件大小（content-length属性就是文件大小）
        res.setContentLength((int) file.length());
        // 20161211: js修改为只要没有contenttype参数就不在返回结果里加content-type，
        // 如果需要以图片的方式下载，则请手动传参CONTENT_TYPE_DEFAULT
        if (!CommonUtils.isStringEmpty(contentType))
            res.setContentType(contentType);

        // http头中放入文件名（支持中文文件名）
        // 【说明】：Content-Disposition 属性是作为对下载文件的一个标识字段，在rfc2616 章节19.5 Additional Features中
        //          有介绍，具体介绍请看 http://www.rfc-editor.org/rfc/rfc1806.txt，Content-Disposition属性有两种类
        //          型：inline 和 attachment inline ：将文件内容直接显示在页面 attachment：弹出对话框让用户下载
        res.setHeader("Content-disposition", (usedForAttachment ? "attachment" : "inline") + ";filename=\""
                //发给客户的数据先以GBK字符集转换成二进制再以ISO-8859-1的编码形式传回客户端
                //客户端的传过来的数据同(因而服务端要进行逆过程--先获ISO-8859-1的二进制再编码成GBK)
                + (isDoubleByteLang(dispName) ? getGBK2ISO_8859_1Str(dispName) : dispName) + "\"");

        int length = (new Long(file.length())).intValue();
        int bufferSize = 1024;
        BufferedOutputStream output = null;
        BufferedInputStream input = null;
        output = new BufferedOutputStream(res.getOutputStream());
        input = new BufferedInputStream(new FileInputStream(file));
        try {
            int once = 0;
            int total = 0;
            byte[] buffer = new byte[bufferSize];
            do {
                once = input.read(buffer);
                total += once;
                if (once >= 0)
                    output.write(buffer, 0, bufferSize);
            }
            while ((total < length) && (once >= 0));
            res.flushBuffer();
        } catch (Exception e)//maybe user cancelled download
        {
            ex = new Exception("下载出错了," + e.getMessage());
//			e.printStackTrace();
        } finally {
            if (input != null)
                input.close();
            if (output != null)
                output.close();

            if (ex != null)
                throw ex;
        }
    }

    /**
     * 是否双字节语言(字符集),本系统中全当中文(繁或简体)处理.
     *
     * @param s
     * @return
     */
    public static boolean isDoubleByteLang(String s) {
        if (s == null)
            s = "";
        return s.length() < s.getBytes().length;
    }

    /**
     * 将GBK字符以ISO-8859-1的编码方式编码成字符串
     * <pre>如服务端通过HTTP协议传到客户端的字符数据时需此转换</pre>
     *
     * @param s
     * @return
     */
    public static String getGBK2ISO_8859_1Str(String s) {
        return convertEncoding(s, "GBK", "ISO-8859-1");
    }

    /**
     * 将字符串s以originalEncoding的编码方式取出并转换成编码destEncloding.
     *
     * @param s                原字符串
     * @param originalEncoding 原编码
     * @param destEncloding    目的编码
     * @return 目的编码后的字符串
     */
    public static String convertEncoding(String s, String originalEncoding, String destEncloding) {
        try {
            // 三者都不为空时才进行编码转换
            if (s != null && originalEncoding != null && destEncloding != null)
                s = new String(s.getBytes(originalEncoding), destEncloding);
        } catch (Exception e) {
            e.printStackTrace();
        }

        return s;
    }
}
